package p.ithorns.framework.xxljob;

import com.fasterxml.jackson.core.type.TypeReference;
import com.xxl.job.core.glue.GlueTypeEnum;

import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.beans.BeanMap;
import org.springframework.util.CollectionUtils;
import p.ithorns.framework.common.utils.JsonUtil;

import java.text.MessageFormat;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * xxl-job定时任务 工具服务类
 *
 * @author WeiZiDong
 * @since 2023-09-06 17:21:37
 **/
public class XxlJobOperator {

    private final static Logger log = LoggerFactory.getLogger(XxlJobOperator.class);

    private final XxlJobConf.Executor executor;

    private final XxlJobConf.Admin admin;

    private volatile String cookie;

    private final OkHttpClient client;


    public XxlJobOperator(XxlJobConf conf) {
        this.executor = conf.getExecutor();
        this.admin = conf.getAdmin();
        this.client = new OkHttpClient();
    }


    /**
     * 初始化Job
     */
    public void initXxlJob(XxlJobInfo jobInfo) {
        XxlJobInfo req = new XxlJobInfo();
        req.setJobDesc(jobInfo.getJobDesc());
        req.setExecutorHandler(jobInfo.getExecutorHandler());
        req.setScheduleType(jobInfo.getScheduleType());
        req.setJobGroup(jobInfo.getJobGroup());
        req.setTriggerStatus(-1);

        // 获取已存在的实例
        XxlJobInfo existsXxlJob = getXxlJob(req);
        if (existsXxlJob == null) {
            // 不存在，新增一个
            addXxlJob(jobInfo);
        } else {
            String scheduleConf = jobInfo.getScheduleConf();
            if (!scheduleConf.equalsIgnoreCase(existsXxlJob.getScheduleConf())) {
                // 存在，且参数有变化
                existsXxlJob.setScheduleConf(scheduleConf);
                updateXxlJob(existsXxlJob);
            }
            // 启动任务
            if (existsXxlJob.getTriggerStatus() != 1) {
                startXxlJob(existsXxlJob.getId());
            }
        }
    }


    /**
     * 增加执行任务
     *
     * @param xxlJobInfo xxl工作信息
     * @return long
     * @author WeiZiDong
     * @since 2023-09-06 17:22:38
     **/
    public String addXxlJob(XxlJobInfo xxlJobInfo) {
        replenishDefaultField(xxlJobInfo);
        log.info("增加xxl执行任务: {}", xxlJobInfo);
        return (String) operationRequest(XxlJobApi.ADD, xxlJobInfo);
    }

    /**
     * 更新 定时任务
     *
     * @param xxlJobInfo xxl工作信息
     * @author WeiZiDong
     * @since 2023-09-06 17:22:53
     **/
    public void updateXxlJob(XxlJobInfo xxlJobInfo) {
        replenishDefaultField(xxlJobInfo);
        log.info("更新xxl执行任务: {}", xxlJobInfo);
        operationRequest(XxlJobApi.UPDATE, xxlJobInfo);
    }

    /**
     * 开启任务
     *
     * @param jobInfoId 作业信息id
     * @author WeiZiDong
     * @since 2023-09-06 17:23:24
     **/
    public void startXxlJob(long jobInfoId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId((int) jobInfoId);
        log.info("开启xxl执行任务: {}", xxlJobInfo);
        operationRequest(XxlJobApi.START, xxlJobInfo);
    }

    /**
     * 停止任务
     *
     * @param jobInfoId 作业信息id
     * @author WeiZiDong
     * @since 2023-09-06 17:23:11
     **/
    public void stopXxlJob(long jobInfoId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId((int) jobInfoId);
        log.info("停止xxl执行任务: {}", xxlJobInfo);
        operationRequest(XxlJobApi.STOP, xxlJobInfo);
    }

    /**
     * 删除任务
     *
     * @param jobInfoId 作业信息id
     * @author WeiZiDong
     * @since 2023-09-06 17:24:24
     **/
    public void removeXxlJob(long jobInfoId) {
        XxlJobInfo xxlJobInfo = new XxlJobInfo();
        xxlJobInfo.setId((int) jobInfoId);
        log.info("删除xxl执行任务: {}", xxlJobInfo);
        operationRequest(XxlJobApi.REMOVE, xxlJobInfo);
    }


    /**
     * 增加执行任务
     *
     * @return long
     * @author WeiZiDong
     * @since 2023-09-06 17:22:38
     **/
    public List<XxlJobInfo> getXxlJobs(Integer jobGroupId, String executorHandler) {
        XxlJobInfo req = new XxlJobInfo();
        req.setJobGroup(jobGroupId);
        req.setExecutorHandler(executorHandler);
        req.setTriggerStatus(-1);
        return queryRequest(XxlJobApi.LIST, req, XxlJobInfo.class);
    }

    public XxlJobInfo getXxlJob(XxlJobInfo req) {
        List<XxlJobInfo> xxlJobInfos = queryRequest(XxlJobApi.LIST, req, XxlJobInfo.class);
        if (CollectionUtils.isEmpty(xxlJobInfos)) {
            return null;
        }

        return xxlJobInfos.get(0);
        // throw new ServiceException(ErrorCode.XXL_JOB_INFO_GET_FAILED);
    }

    /**
     * 获取执行器id
     * 由于xxl-job会校验执行器id,所以需要将注册名转换为id 使用
     *
     * @return int
     * @author WeiZiDong
     * @since 2023-09-06 17:26:29
     **/
    private int getJobGroupId() {
        XxlJobGroupReq req = new XxlJobGroupReq();
        req.setAppname(executor.getAppName());
        log.info("获取xxl执行id: {}", req);

        List<XxlJobGroup> xxlJobGroupList = queryRequest(XxlJobApi.GROUP_LIST, req, XxlJobGroup.class);
        if (CollectionUtils.isEmpty(xxlJobGroupList)) {
            throw new XxlJobException(XxlJobConst.XXL_JOB_GROUP_GET_EXECUTOR_FAILED);
        }
        return xxlJobGroupList.get(0).getId();
    }

    /**
     * 以AppName和Title获取任务组
     *
     * @return List<XxlJobGroup>
     */
    public List<XxlJobGroup> getJobGroup() {
        XxlJobGroupReq req = new XxlJobGroupReq();
        req.setAppname(executor.getAppName());
        req.setTitle(executor.getTitle());
        return queryRequest(XxlJobApi.GROUP_LIST, req, XxlJobGroup.class);
    }

    /**
     * 注册JobGroup
     */
    public void registerJobGroup() {
        XxlJobGroupReq req = new XxlJobGroupReq();
        req.setAppname(executor.getAppName());
        req.setTitle(executor.getTitle());

        int addressType = executor.getAddressType();
        req.setAddressType(addressType);

        String addressList = executor.getAddressList();
        if (addressType == 1) {
            if (Strings.isBlank(addressList)) {
                throw new XxlJobException("手动录入模式下,执行器地址列表不能为空");
            }
            req.setAddressList(addressList);
        }

        operationRequest(XxlJobApi.GROUP_SAVE, req);
    }

    /**
     * 查询是否存在
     * 使用AppName和Title
     */
    public boolean checkIfGroupExists() {
        return getJobGroup().stream()
                .anyMatch(xxlJobGroup -> xxlJobGroup.getAppname().equals(executor.getAppName())
                        && xxlJobGroup.getTitle().equals(executor.getTitle()));
    }


    /**
     * 补充 定时任务的默认值
     *
     * @param xxlJobInfo xxl工作信息
     * @author WeiZiDong
     * @since 2023-09-06 17:25:18
     **/
    private void replenishDefaultField(XxlJobInfo xxlJobInfo) {
        if (xxlJobInfo.getJobGroup() == null) {
            xxlJobInfo.setJobGroup(getJobGroupId());
        }
        //默认 随机分配
        if (xxlJobInfo.getExecutorRouteStrategy() == null) {
            xxlJobInfo.setExecutorRouteStrategy("RANDOM");
        }
        //使用 BEAN方式
        if (StringUtils.isBlank(xxlJobInfo.getGlueType())) {
            xxlJobInfo.setGlueType(GlueTypeEnum.BEAN.getDesc());
        }
        //任务过期策略   啥也不做
        if (StringUtils.isBlank(xxlJobInfo.getMisfireStrategy())) {
            xxlJobInfo.setMisfireStrategy("DO_NOTHING");
        }
        //任务阻塞策略  COVER_EARLY--覆盖之前  SERIAL_EXECUTION --- 串行
        if (StringUtils.isBlank(xxlJobInfo.getExecutorBlockStrategy())) {
            xxlJobInfo.setExecutorBlockStrategy("SERIAL_EXECUTION");
        }
        if (StringUtils.isBlank(xxlJobInfo.getAuthor())) {
            xxlJobInfo.setAuthor(admin.getLoginUsername());
        }
    }

    /**
     * 查询请求
     */
    private <R, U> List<R> queryRequest(XxlJobApi api, U param, Class<R> rClass) {
        String body = doRequest(api, param);
        XxlJobApiResult<List<R>> result = JsonUtil.fromJson(body, new TypeReference<XxlJobApiResult<List<R>>>() {
        });

        if (null == result) {
            throw new XxlJobException(XxlJobConst.XXL_JOB_QUERY_FAILED);
        }

        int code = result.getCode();
        if (200 != code) {
            log.error("ithorns-boot-starter-xxl-job-admin: {}", result.getMsg());
            throw new XxlJobException(XxlJobConst.XXL_JOB_QUERY_FAILED);
        }
        List<R> data = result.getData();
        if (CollectionUtils.isEmpty(data)) {
            return Collections.emptyList();
        }

        return data;
    }

    /**
     * 操作请求
     */
    private <U> Object operationRequest(XxlJobApi api, U param) {
        String body = doRequest(api, param);
        XxlJobCommonResp resp = JsonUtil.fromJson(body, XxlJobCommonResp.class);
        if (null == resp) {
            throw new XxlJobException("ithorns-boot-starter-xxl-job-admin响应失败.");
        }

        if (200 != resp.getCode()) {
            throw new XxlJobException(resp.getMsg());
        }

        return resp.getContent();
    }

    /**
     * 执行请求
     */
    private <U> String doRequest(XxlJobApi api, U paramObj) {
        String apiDesc = api.getDesc();
        String url = admin.getAddresses() + api.getUrl();

        FormBody formBody = buildFormBody(paramObj);
        log.info("ithorns-boot-starter-xxl-job[{}] -> 请求参数: {}", apiDesc, formBody.toString());

        Request request = new Request.Builder()
                .url(url)
                .post(formBody)
                .header("Cookie", getCookie())
 //             .header(AUTHENTICATION, appToken);
//              .header(X_APP_ID, String.valueOf(context.getId()));
//              .header(XxlJobRemotingUtil.XXL_JOB_ACCESS_TOKEN, conf.getAccessToken());
                .build();

        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                ResponseBody body = response.body();
                assert body != null;
                return body.string();
            }
            String errMsg = MessageFormat.format("调用xxl-job-admin[{0}]失败: {1}", apiDesc, response.body());
            throw new XxlJobException(errMsg);

        } catch (Exception e) {
            throw new XxlJobException(e.getMessage());
        }
    }


    /**
     * 获取cookie
     *
     * @return {@link String }
     * @author WeiZiDong
     * @since 2023-09-06 17:26:32
     **/
    private synchronized String getCookie() {
        if (null != this.cookie && this.cookie.length() > 0) {
            return this.cookie;
        }

        String url = admin.getAddresses() + XxlJobApi.LOGIN.getUrl();
        XxlJobLoginReq req = new XxlJobLoginReq(admin.getLoginUsername(), admin.getLoginPwd());
        log.info("获取xxl cookie,请求参数：{}", req);

        FormBody formBody = buildFormBody(req);
        log.info("ithorns-boot-starter-xxl-job[{}] -> 请求参数: {}", url, formBody);

        Request request = new Request.Builder()
                .url(url).post(formBody)
                .build();
        try (Response response = client.newCall(request).execute()) {
            if (response.isSuccessful()) {
                List<String> cookies = response.request().headers("Cookie");
                log.info("获取xxl cookie成功,返回信息:{}", cookies);
                StringBuilder sb = new StringBuilder();
                for (String cookie : cookies) {
                    sb.append(cookie);
                }
                return sb.toString();
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        throw new XxlJobException(XxlJobConst.XXL_JOB_GET_COOKIE_FAILED);
    }

    private FormBody buildFormBody(Object paramObj) {
        FormBody.Builder builder = new FormBody.Builder();
        BeanMap beanMap = BeanMap.create(paramObj);
        Set<Map.Entry<String, Object>> entries = beanMap.entrySet();
        for (Map.Entry<String, Object> entry : entries) {
            String key = entry.getKey();
            String value = entry.getValue().toString();
            builder.add(key, value);
        }
        return builder.build();
    }

}